#include "stdafx.h"
#include "ClientApp.h"
#include "HCM/AssemblyRegistration.h"

using namespace Hiranipra;
using namespace Hiranipra::Client;
using namespace Hiranipra::HCM;
using namespace Hiranipra::Platform;
using namespace Hiranipra::Platform::Graphics;
using namespace Hiranipra::Platform::IO;

ClientApp::ClientApp()
{
}

ClientApp::~ClientApp()
{
	RELEASE( _app );
	RELEASE( _graphics );
}

bool ClientApp::RegisterAssembly( wstring path )
{
	return HCMRegisterAssembly( _hcmRegistry, path );
}

VertexElement cubeDeclElements[] = {
	VertexElement( VertexFormatVector3, VertexUsagePosition ),
	VertexElement( VertexFormatVector2, VertexUsageTextureCoordinate )
};
VERTEX_DECLARATION( cubeDecl, cubeDeclElements );

class ClientApp::MainRunner : public Runnable, HANDLES( IOEvent )
{
public:
	ClientApp*		App;
	bool			Stop;

	MainRunner( ClientApp* app ) :
		App( app ), Stop( false )
	{
	}
	
	void run()
	{
		IApplication* app = App->GetApplication();
		
		App->_ioQueue->EventSource.Add( this );

		/*IFileStream* sourceStream = App->_fs->OpenRead( L"test.txt" );
		MemoryBuffer* buffer = new MemoryBuffer( 123 );
		uint32 requestId = App->_ioQueue->BeginRead( sourceStream, buffer );*/

		// Wait until main window created
		while( App->_mainWindow == NULL ) Thread::yield();

		// Create device
		Adapter adapter;
		Resolution resolution;
		resolution.ColorBits = 32;
		resolution.DepthBits = 16;
		resolution.StencilBits = 0;
		DeviceParameters parameters;
		parameters.Window = App->_mainWindow;
		IGraphicsDevice* device = App->_graphics->CreateDevice( adapter, resolution, parameters );
		App->_device = device;

		IFileStream* stream = App->_fs->OpenRead( L"test.bmp" );
		ITextureLoader* textureLoader = device->GetTextureLoader();
		ITexture* texture = textureLoader->LoadTexture( stream );
		RELEASE( stream );

		float vertices[] = {
		  -1.0f, -1.0f,  1.0f,   0.0f, 0.0f,
		   1.0f, -1.0f,  1.0f,   1.0f, 0.0f,
		   1.0f,  1.0f,  1.0f,   1.0f, 1.0f,
		  -1.0f,  1.0f,  1.0f,   0.0f, 1.0f,

		  -1.0f, -1.0f, -1.0f,   1.0f, 0.0f,
		  -1.0f,  1.0f, -1.0f,   1.0f, 1.0f,
		   1.0f,  1.0f, -1.0f,   0.0f, 1.0f,
		   1.0f, -1.0f, -1.0f,   0.0f, 0.0f,

		  -1.0f,  1.0f, -1.0f,   0.0f, 1.0f,
		  -1.0f,  1.0f,  1.0f,   0.0f, 0.0f,
		   1.0f,  1.0f,  1.0f,   1.0f, 0.0f,
		   1.0f,  1.0f, -1.0f,   1.0f, 1.0f,

		  -1.0f, -1.0f, -1.0f,   1.0f, 1.0f,
		   1.0f, -1.0f, -1.0f,   0.0f, 1.0f,
		   1.0f, -1.0f,  1.0f,   0.0f, 0.0f,
		  -1.0f, -1.0f,  1.0f,   1.0f, 0.0f,

		   1.0f, -1.0f, -1.0f,   1.0f, 0.0f,
		   1.0f,  1.0f, -1.0f,   1.0f, 1.0f,
		   1.0f,  1.0f,  1.0f,   0.0f, 1.0f,
		   1.0f, -1.0f,  1.0f,   0.0f, 0.0f,

		  -1.0f, -1.0f, -1.0f,   0.0f, 0.0f,
		  -1.0f, -1.0f,  1.0f,   1.0f, 0.0f,
		  -1.0f,  1.0f,  1.0f,   1.0f, 1.0f,
		  -1.0f,  1.0f, -1.0f,   0.0f, 1.0f
		};
		IVertexBuffer* vb = device->CreateVertexBuffer( BufferDefault, &cubeDecl, 24, vertices );

		uint64 timeFrequency = app->GetTimeFrequency();
		float angle = 0.0f;
		while( this->Stop == false )
		{
			uint64 timeElapsed = app->GetTimeElapsed();
			double elapsed = timeElapsed / ( double )timeFrequency;

			device->BeginFrame( ClearColor | ClearDepth );

			device->SetTexture( 0, texture );
			device->DrawDummy( angle );
			device->RenderBuffer( PrimitiveQuads, vb );
			angle += ( float )( 360.0 * elapsed );

			device->EndFrame();
		}

		RELEASE( texture );
		RELEASE( vb );
	}

	DEFINE_HANDLER( ClientApp::MainRunner, IOEvent )
	{
		switch( ev.Name )
		{
		case IOEvent::ReadCompleted:
			break;
		case IOEvent::WriteCompleted:
			break;
		}
	}
};

class ClientApp::WindowRunner : public Runnable, HANDLES( WindowEvent )
{
public:
	ClientApp*			App;
	vector<IWindow*>	Windows;
	bool				Stop;

	WindowRunner( ClientApp* app ) :
		App( app ), Stop( false )
	{
	}
	
	void run()
	{
		IApplication* app = App->GetApplication();

		IWindow* mainWindow;
		{
			Guard<FastMutex> g( App->_windowLock );
			
			WindowParameters wp;
			wp.DefaultTitle = L"Hiranipra Client";
			wp.DefaultSize = Size2D( 800, 600 );
			wp.DefaultState = Normal;
			mainWindow = app->NewWindow( wp );
			mainWindow->EventSource.Add( this );

			App->_mainWindow = mainWindow;
			mainWindow->Retain();
			App->_windows.push_back( mainWindow );
		}
		
		while( this->Stop == false )
		{
			app->Update();
			{
				Guard<FastMutex> g( App->_windowLock );
				for( vector<IWindow*>::iterator it = App->_windows.begin(); it != App->_windows.end(); it++ )
				{
					IWindow* window = (*it);
					if( window->IsClosed() == true )
					{
						App->_windows.erase( it );
						RELEASE( window );
						break;
					}
				}
			}
			if( mainWindow->IsClosed() == true )
				break;
			Thread::sleep( 25 );
		}

		RELEASE( mainWindow );
	}

	DEFINE_HANDLER( ClientApp::WindowRunner, WindowEvent )
	{
		switch( ev.Name )
		{
		case WindowEvent::SizeChanged:
			break;
		case WindowEvent::Closed:
			break;
		}
	}
};

class ClientApp::NetworkRunner : public Runnable, HANDLES( NetEvent )
{
public:
	ClientApp*			App;
	bool				Stop;

	NetworkRunner( ClientApp* app ) :
		App( app ), Stop( false )
	{
	}
	
	void run()
	{
		IApplication* app = App->GetApplication();

		NetClient* netClient = new NetClient();
		App->_netClient = netClient;
		netClient->EventSource.Add( this );

		Address address( Address::LocalHost, 9800 );
		netClient->Connect( address, 6 );

		while( this->Stop == false )
		{
			if( netClient->Poll( 150 ) == false )
			{
				// Timed out? No packets?
			}
		}

		RELEASE( netClient );
	}

	DEFINE_HANDLER( ClientApp::NetworkRunner, NetEvent )
	{
		switch( ev.Name )
		{
		case NetEvent::Connected:
			break;
		case NetEvent::Disconnected:
			break;
		case NetEvent::Received:
			break;
		}
	}
};

int ClientApp::Start( wstring appPath, vector<wstring> args )
{
	_app = _hcmRegistry.AllocateDefault<IApplication>( COMPONENT_TYPE( IApplication ) );
	_fs = _hcmRegistry.AllocateDefault<IFileSystem>( COMPONENT_TYPE( IFileSystem ) );
	_graphics = _hcmRegistry.AllocateDefault<IGraphicsSystem>( COMPONENT_TYPE( IGraphicsSystem ) );

	_fs->Setup( appPath );

	ISystemInformation* sysInfo = _app->QuerySystemInformation();

	_ioQueue = new IOQueue( _fs );

	WindowRunner* windowRunner = new WindowRunner( this );
	Task windowRunnerTask( windowRunner );
	Thread windowRunnerThread( windowRunnerTask );

	NetworkRunner* netRunner = new NetworkRunner( this );
	Task netRunnerTask( netRunner );
	Thread netRunnerThread( netRunnerTask );

	MainRunner* mainRunner = new MainRunner( this );
	Task mainRunnerTask( mainRunner );
	Thread mainRunnerThread( mainRunnerTask );

	windowRunnerThread.wait();
	mainRunner->Stop = true;
	mainRunnerThread.wait();
	netRunner->Stop = true;
	netRunnerThread.wait();

	// Close all windows
	{
		RELEASE( _device );

		Guard<FastMutex> g( _windowLock );
		while( _windows.size() > 0 )
		{
			IWindow* window = _windows.back();
			_windows.pop_back();
			window->Close();
			RELEASE( window );
		}
	}

	RELEASE( _ioQueue );

	RELEASE( _graphics );
	RELEASE( _fs );
	RELEASE( _app );

	return 0;
}
